/*
 * Copyright 2002-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.test.web.reactive.server;

import org.hamcrest.Matcher;
import org.hamcrest.MatcherAssert;
import org.springframework.http.HttpStatus;
import org.springframework.test.util.AssertionErrors;

import java.util.function.Consumer;

/**
 * Assertions on the response status.
 *
 * @author Rossen Stoyanchev
 * @see WebTestClient.ResponseSpec#expectStatus()
 * @since 5.0
 */
public class StatusAssertions {

    private final ExchangeResult exchangeResult;

    private final WebTestClient.ResponseSpec responseSpec;


    StatusAssertions(ExchangeResult result, WebTestClient.ResponseSpec spec) {
        this.exchangeResult = result;
        this.responseSpec = spec;
    }


    /**
     * Assert the response status as an {@link HttpStatus}.
     */
    public WebTestClient.ResponseSpec isEqualTo(HttpStatus status) {
        return isEqualTo(status.value());
    }

    /**
     * Assert the response status as an integer.
     */
    public WebTestClient.ResponseSpec isEqualTo(int status) {
        int actual = this.exchangeResult.getStatus().value();
        this.exchangeResult.assertWithDiagnostics(() -> AssertionErrors.assertEquals("Status", status, actual));
        return this.responseSpec;
    }

    /**
     * Assert the response status code is {@code HttpStatus.OK} (200).
     */
    public WebTestClient.ResponseSpec isOk() {
        return assertStatusAndReturn(HttpStatus.OK);
    }

    /**
     * Assert the response status code is {@code HttpStatus.CREATED} (201).
     */
    public WebTestClient.ResponseSpec isCreated() {
        HttpStatus expected = HttpStatus.CREATED;
        return assertStatusAndReturn(expected);
    }

    /**
     * Assert the response status code is {@code HttpStatus.ACCEPTED} (202).
     */
    public WebTestClient.ResponseSpec isAccepted() {
        return assertStatusAndReturn(HttpStatus.ACCEPTED);
    }

    /**
     * Assert the response status code is {@code HttpStatus.NO_CONTENT} (204).
     */
    public WebTestClient.ResponseSpec isNoContent() {
        return assertStatusAndReturn(HttpStatus.NO_CONTENT);
    }

    /**
     * Assert the response status code is {@code HttpStatus.FOUND} (302).
     */
    public WebTestClient.ResponseSpec isFound() {
        return assertStatusAndReturn(HttpStatus.FOUND);
    }

    /**
     * Assert the response status code is {@code HttpStatus.SEE_OTHER} (303).
     */
    public WebTestClient.ResponseSpec isSeeOther() {
        return assertStatusAndReturn(HttpStatus.SEE_OTHER);
    }

    /**
     * Assert the response status code is {@code HttpStatus.NOT_MODIFIED} (304).
     */
    public WebTestClient.ResponseSpec isNotModified() {
        return assertStatusAndReturn(HttpStatus.NOT_MODIFIED);
    }

    /**
     * Assert the response status code is {@code HttpStatus.TEMPORARY_REDIRECT} (307).
     */
    public WebTestClient.ResponseSpec isTemporaryRedirect() {
        return assertStatusAndReturn(HttpStatus.TEMPORARY_REDIRECT);
    }

    /**
     * Assert the response status code is {@code HttpStatus.PERMANENT_REDIRECT} (308).
     */
    public WebTestClient.ResponseSpec isPermanentRedirect() {
        return assertStatusAndReturn(HttpStatus.PERMANENT_REDIRECT);
    }

    /**
     * Assert the response status code is {@code HttpStatus.BAD_REQUEST} (400).
     */
    public WebTestClient.ResponseSpec isBadRequest() {
        return assertStatusAndReturn(HttpStatus.BAD_REQUEST);
    }

    /**
     * Assert the response status code is {@code HttpStatus.UNAUTHORIZED} (401).
     */
    public WebTestClient.ResponseSpec isUnauthorized() {
        return assertStatusAndReturn(HttpStatus.UNAUTHORIZED);
    }

    /**
     * Assert the response status code is {@code HttpStatus.FORBIDDEN} (403).
     *
     * @since 5.0.2
     */
    public WebTestClient.ResponseSpec isForbidden() {
        return assertStatusAndReturn(HttpStatus.FORBIDDEN);
    }

    /**
     * Assert the response status code is {@code HttpStatus.NOT_FOUND} (404).
     */
    public WebTestClient.ResponseSpec isNotFound() {
        return assertStatusAndReturn(HttpStatus.NOT_FOUND);
    }

    /**
     * Assert the response error message.
     */
    public WebTestClient.ResponseSpec reasonEquals(String reason) {
        String actual = this.exchangeResult.getStatus().getReasonPhrase();
        String message = "Response status reason";
        this.exchangeResult.assertWithDiagnostics(() -> AssertionErrors.assertEquals(message, reason, actual));
        return this.responseSpec;
    }

    /**
     * Assert the response status code is in the 1xx range.
     */
    public WebTestClient.ResponseSpec is1xxInformational() {
        return assertSeriesAndReturn(HttpStatus.Series.INFORMATIONAL);
    }

    /**
     * Assert the response status code is in the 2xx range.
     */
    public WebTestClient.ResponseSpec is2xxSuccessful() {
        return assertSeriesAndReturn(HttpStatus.Series.SUCCESSFUL);
    }

    /**
     * Assert the response status code is in the 3xx range.
     */
    public WebTestClient.ResponseSpec is3xxRedirection() {
        return assertSeriesAndReturn(HttpStatus.Series.REDIRECTION);
    }

    /**
     * Assert the response status code is in the 4xx range.
     */
    public WebTestClient.ResponseSpec is4xxClientError() {
        return assertSeriesAndReturn(HttpStatus.Series.CLIENT_ERROR);
    }

    /**
     * Assert the response status code is in the 5xx range.
     */
    public WebTestClient.ResponseSpec is5xxServerError() {
        HttpStatus.Series expected = HttpStatus.Series.SERVER_ERROR;
        return assertSeriesAndReturn(expected);
    }

    /**
     * Match the response status value with a Hamcrest matcher.
     *
     * @param matcher the matcher to use
     * @since 5.1
     */
    public WebTestClient.ResponseSpec value(Matcher<Integer> matcher) {
        int value = this.exchangeResult.getStatus().value();
        this.exchangeResult.assertWithDiagnostics(() -> MatcherAssert.assertThat("Response status", value, matcher));
        return this.responseSpec;
    }

    /**
     * Match the response status value with a Hamcrest matcher.
     *
     * @param consumer the matcher to use
     * @since 5.1
     */
    public WebTestClient.ResponseSpec value(Consumer<Integer> consumer) {
        int value = this.exchangeResult.getStatus().value();
        this.exchangeResult.assertWithDiagnostics(() -> consumer.accept(value));
        return this.responseSpec;
    }


    private WebTestClient.ResponseSpec assertStatusAndReturn(HttpStatus expected) {
        HttpStatus actual = this.exchangeResult.getStatus();
        this.exchangeResult.assertWithDiagnostics(() -> AssertionErrors.assertEquals("Status", expected, actual));
        return this.responseSpec;
    }

    private WebTestClient.ResponseSpec assertSeriesAndReturn(HttpStatus.Series expected) {
        HttpStatus status = this.exchangeResult.getStatus();
        this.exchangeResult.assertWithDiagnostics(() -> {
            String message = "Range for response status value " + status;
            AssertionErrors.assertEquals(message, expected, status.series());
        });
        return this.responseSpec;
    }

}
